import { Color3 } from '@babylonjs/core/Maths/math.color';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { PointsCloudSystem } from '@babylonjs/core/Particles/pointsCloudSystem';
import { Scene } from '@babylonjs/core/scene';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { AnimationFrame, ITimer } from '../animation/animation-frame';
import { AnimationGroup } from '@babylonjs/core/Animations/animationGroup';
import { WebXRCamera } from '@babylonjs/core/XR/webXRCamera';
import { UniqueIdGenerator } from '@babylonjs/core/Misc/uniqueIdGenerator';
import { Camera } from '@babylonjs/core/Cameras/camera';
import { Axis } from '@babylonjs/core/Maths/math.axis';
import { BabylonContainer } from '../base/babylon-container';
import { PointerInfo } from '@babylonjs/core/Events/pointerEvents';
import { PickingInfo } from '@babylonjs/core/Collisions/pickingInfo';


/**
 * 沉浸模式支持
 */
const support: { xr?: boolean, vr?: boolean, ar?: boolean } = { xr: !!navigator.xr, vr: undefined, ar: undefined };

/**
 * 点云可选参数对象接口
 */
interface IPointsOptions {
    pointSize?: number,
    emissiveColor?: Color3
}

/**
 * 动画分段步骤对象
 * @augments [0] 动画对象 
 * @augments [1] 起始进程值 
 * @augments [2] 结束进程值 
 * @augments [3] 进程速度 
 * @augments [4] 步骤开始时的回调函数（可返回速度倍率） 
 * @augments [5] 步骤进行时的回调函数（可返回速度倍率）
 * @augments [6] 步骤结束时的回调函数（可返回速度倍率）
 */
interface ISequenceStep {
    [0]: AnimationGroup | null | undefined,
    [1]: number,
    [2]: number,
    [3]: number,
    [4]?: { (): number | void },
    [5]?: { (prg: number): number | void },
    [6]?: { (): number | void }
}


/**
 * babylonjs相关的辅助方法
 */
class Helper {

    private static _tv0 = new Vector3();
    private static _tq0 = new Quaternion();

    /**
     * 初始化支持xr状态（如果首帧需要查询xr支持，应手动调用）
     */
    public static async initialize(): Promise<void> {
        if (support.xr) {
            support.vr = await navigator.xr!.isSessionSupported('immersive-vr');
            support.ar = await navigator.xr!.isSessionSupported('immersive-ar');
        } else {
            support.vr = false;
            support.ar = false;
        }
    }

    /**
     * 绑定对象到相机（位置及Y轴旋转）
     * @param camera 相机
     * @param target 目标对象
     */
    public static bindToCamera(camera: Camera, target: TransformNode): void {
        const dir = this._tv0;
        const rot = this._tq0;
        target.position.copyFrom(camera.position);
        this.cameraDirection(camera, dir);
        dir.y = 0;
        dir.normalize();
        BabylonContainer.isRightHand ? Quaternion.FromLookDirectionRHToRef(dir, Axis.Y, rot) : Quaternion.FromLookDirectionLHToRef(dir, Axis.Y, rot);
        target.rotationQuaternion ? target.rotationQuaternion.copyFrom(rot) : target.rotationQuaternion = rot.clone();
    }

    /**
     * 获取相机视线方向
     * @param camera 相机
     * @param out 输出结果
     * @returns 相机视线方向
     */
    public static cameraDirection(camera: Camera, out: Vector3): Vector3 {
        if (camera instanceof WebXRCamera) {
            camera.rigCameras[0].getDirectionToRef(BabylonContainer.forward, out);
        } else {
            camera.getDirectionToRef(BabylonContainer.forward, out);
        }
        return out;
    }

    /**
     * 绘制点云
     * @param name 名称 
     * @param data 点坐标数据
     * @param scene 场景
     * @param options 可选参数对象
     * @returns 点云
     */
    public static drawPoints(name: string, data: Vector3[] | Float32Array, scene: Scene, options?: IPointsOptions): PointsCloudSystem {
        const op = options || {};
        const pcs = new PointsCloudSystem(name, op.pointSize || 10, scene);
        const isVec = typeof data[0] !== 'number';
        const pfunc = (particle: any, i: number) => {
            const v = isVec ? data[i] : new Vector3((data as Float32Array)[i * 3], (data as Float32Array)[i * 3 + 1], (data as Float32Array)[i * 3 + 2]);
            particle.position = v;
        };
        pcs.addPoints(isVec ? data.length : data.length / 3, pfunc);
        pcs.buildMeshAsync().then(() => {
            (pcs.mesh!.material as StandardMaterial).emissiveColor = op.emissiveColor || new Color3(1, 1, 1);
        });
        return pcs;
    }

    /**
     * 从模型中提取子模型（改变模型结构）
     * @param name 子模型名称
     * @param scene 所在场景
     * @param generateContainer 是否创建容器
     * @returns 仅含子模型的容器对象
     */
    public static extractModel(name: string, scene: Scene, generateContainer?: boolean): TransformNode | null {
        const md = this.extractModleInternal(name, scene);
        if (!md) return null;
        if (!generateContainer) return md;
        const p = new TransformNode(md.name + '-' + UniqueIdGenerator.UniqueId, md.getScene());
        p.position.copyFrom(md.absolutePosition);
        p.rotationQuaternion = new Quaternion().copyFrom(md.absoluteRotationQuaternion);
        md.setParent(p);
        return p;
    }

    /**
     * @internal
     * 获取模型并根据当前坐标系处理z缩放
     * @param name 模型名称
     * @param scene 所在场景
     * @returns 模型
     */
    private static extractModleInternal(name: string, scene: Scene): TransformNode | null {
        const tar = scene.getNodeByName(name) as TransformNode;
        if (!tar) return null;
        const ps: TransformNode[] = [];
        this.isChild(tar, undefined, ps);
        const tp = ps[ps.length - 1];
        if (tp.id === tar.id) return tar;
        if (tp.id !== '__root__') return tar;
        if (tp.scaling.z > 0) return tar;
        tar.scaling.z *= -1;
        return tar;
    }

    /**
     * 获取对象包括自身（若可用，集合第一个）的所有网格子级
     * @param target 目标对象
     * @param directDescendantsOnly 是否只取第一级
     * @returns 包含自身（若可用，第一个）的所有网格子级
     */
    public static getChildMeshes(target: TransformNode, directDescendantsOnly?: boolean): AbstractMesh[] {
        const ms = target.getChildMeshes(directDescendantsOnly);
        if (target instanceof AbstractMesh) {
            ms.unshift(target);
        }
        return ms;
    }

    /**
     * 为目标创建动画组对象
     * @param name 动画组对象的名称
     * @param target 目标对象
     * @param scene 所在场景
     * @returns 动画组对象
     */
    public static generateAnimationGroup(name: string, target: TransformNode, scene: Scene): AnimationGroup {
        const ani = new AnimationGroup(name, scene);
        target.getChildTransformNodes().forEach(t => {
            for (const a of t.animations) {
                ani.addTargetedAnimation(a, t);
            }
        });
        return ani;
    }

    /**
     * 是否属于目标对象
     * @param child 被检查对象
     * @param target 目标对象
     * @param ancestors 所有父级
     * @returns 父级层级(-1表示不是对象子级，0表示就是对象自身...)
     */
    public static isChild(child: TransformNode, target?: TransformNode, ancestors?: TransformNode[]): number {
        let p = child as any;
        if (ancestors) ancestors.push(p);
        let dph = 0;
        while (p) {
            if (target) {
                if (p.id === target.id) {
                    if (ancestors) ancestors.push(p);
                    return dph;
                }
            }
            p = p.parent;
            if (ancestors && p) ancestors.push(p);
            dph++;
        }
        return target ? -1 : dph;
    }

    /**
     * 相机是否标记为通用
     * @param camera 目标相机
     * @returns 标记为通用
     */
    public static isCameraGeneral(camera: Camera | undefined | null): boolean {
        if (!camera) return false;
        return camera.metadata && camera.metadata.isGeneral;
    }

    /**
     * 获取模型自定义属性
     * @param model 模型对象
     * @param key 属性键名
     * @returns 属性值
     */
    public static modelProperty<T>(model: TransformNode, key: string): T | null | undefined {
        const md = model.metadata;
        if (!md) return null;
        const gltf = md.gltf;
        if (!gltf) return null;
        const exr = gltf.extras;
        if (!exr) return null;
        return exr[key] as unknown as T;
    }

    /**
     * 标记相机为通用（可用于框架中的onActiveCameraChanged事件）
     * @param camera 目标相机
     * @param cancel 撤销相机的通用标记
     * @param force 强制更改（强制更改存在metadata.isGeneral）
     */
    public static markGeneralCamera(camera: Camera, cancel?: boolean, force?: boolean): void {
        if (camera.metadata && camera.metadata.isGeneral) {
            if (force) camera.metadata.isGeneral = !cancel;
        } else {
            camera.metadata = camera.metadata || {};
            camera.metadata.isGeneral = !cancel;
        }
    }

    /**
     * 根据名称获取节点对象集合中的节点对象
     * @param name 名称或名称的一部分
     * @param nodes 节点对象集合
     * @param out 输出结果集合
     * @param precisely 是否精确匹配
     * @returns 精确匹配时匹配到的节点对象
     */
    public static nodeInArray(name: string, nodes: TransformNode[], out?: TransformNode[], precisely?: boolean): TransformNode | null {
        const reg = new RegExp(name, 'i');
        for (const node of nodes) {
            if (out) {
                if (precisely ? (node.name === name) : (reg.test(node.name))) out.push(node);
            } else {
                if (precisely ? (node.name === name) : (reg.test(node.name))) return node;
            }
        }
        return null;
    }

    /**
     * 是否支持xr
     */
    public static get isXrSupported(): boolean | undefined {
        return support.xr;
    }

    /**
     * 是否支持vr
     */
    public static get isVrSupported(): boolean | undefined {
        if (support.vr === undefined) console.warn('The value has not been initialized!');
        return support.vr;
    }

    /**
     * 是否支持ar
     */
    public static get isArSupported(): boolean | undefined {
        if (support.ar === undefined) console.warn('The value has not been initialized!');
        return support.ar;
    }

    /**
     * 是否处于沉浸模式
     * @param scene 所在场景
     * @returns 是否处于沉浸模式
     */
    public static isImmersive(scene: Scene): boolean {
        if (!scene.activeCamera) return false;
        if (scene.activeCamera instanceof WebXRCamera) return true;
        if (!scene.activeCameras) return false;
        if (scene.activeCameras.length === 0) return false;

        let sus = false;
        for (const cam of scene.activeCameras) {
            if (cam.uniqueId !== scene.activeCamera.uniqueId) continue;
            sus = true;
            break;
        }
        if (!sus) return false;

        for (const cam of scene.activeCameras) {
            if (cam instanceof WebXRCamera) return true;
        }
        return false;
    }

    /**
     * 动画分步
     * @param request 动画帧对象
     * @param loop 是否循环
     * @param sequence_ani_s_e_sp_sCall_pCall_eCall 分段步骤序列
     */
    public static playSequence(request: ITimer, loop: boolean, ...sequence_ani_s_e_sp_sCall_pCall_eCall: Array<ISequenceStep>): void {
        const length = sequence_ani_s_e_sp_sCall_pCall_eCall.length;
        if (!length) return;
        let prg = 0;
        let index = 0;
        let animator = sequence_ani_s_e_sp_sCall_pCall_eCall[index][0];
        let start = sequence_ani_s_e_sp_sCall_pCall_eCall[index][1];
        let end = sequence_ani_s_e_sp_sCall_pCall_eCall[index][2];
        let delta = end - start;
        let spd = sequence_ani_s_e_sp_sCall_pCall_eCall[index][3];
        let startCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][4];
        let progressCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][5];
        let endCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][6];
        let spdR: number = 1;
        const duration = animator ? animator.to - animator.from : 0;
        if (startCall) {
            const r = startCall()!;
            if (r) spdR = r;
        }
        const ani = () => {
            prg += spd * spdR!;
            prg = prg < 0 ? 0 : (prg > 1 ? 1 : prg);
            let t = delta * prg + start;
            if (animator) animator.goToFrame(t * duration);
            if (progressCall) {
                const r = progressCall(prg);
                if (r) spdR = r;
            }
            if (prg === 1) {
                prg = 0;
                if (endCall) {
                    const r = endCall();
                    if (r) spdR = r;
                }
                index++;
                if (index === length) {
                    if (loop) {
                        index = 0;
                    } else {
                        return true;
                    }
                }
                animator = sequence_ani_s_e_sp_sCall_pCall_eCall[index][0];
                start = sequence_ani_s_e_sp_sCall_pCall_eCall[index][1];
                end = sequence_ani_s_e_sp_sCall_pCall_eCall[index][2];
                delta = end - start;
                spd = sequence_ani_s_e_sp_sCall_pCall_eCall[index][3];
                startCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][4];
                progressCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][5];
                endCall = sequence_ani_s_e_sp_sCall_pCall_eCall[index][6];
                if (startCall) {
                    const r = startCall()!;
                    if (r) spdR = r;
                }
            }
        };
        AnimationFrame.request(ani, 16, 200, request);
    }

    /**
     * 获取拾取信息
     * @param e 原始指针事件
     * @param scene 所在场景
     * @returns 拾取信息
     */
    public static pickInfo(e: PointerInfo, scene: Scene): PickingInfo | null {
        let pickInfo = e.pickInfo;
        if (!pickInfo) return null;
        if (!pickInfo.ray) return null;
        if ((!pickInfo.pickedMesh) && pickInfo.ray) {
            const pi = scene.pickWithRay(pickInfo.ray);
            if (pi) {
                pickInfo = pi;
            }
        }
        return pickInfo;
    }

    /**
     * 从给定的TransformNode数组中筛选出层级最高的节点
     * @param nodes TransformNode数组，用于筛选根节点
     * @returns 返回根节点的数组
     */
    public static rootTransformNode(nodes: TransformNode[]): TransformNode[] {
        const out: TransformNode[] = [];
        for (const node of nodes) {
            if (node.parent) {
                const index = nodes.indexOf(node.parent as TransformNode);
                if (index === -1) {
                    out.push(node);
                }
            } else {
                out.push(node);
            }
        }
        return out;
    }

    /**
     * 设置场景中网格的属性
     * @param target 目标场景或节点
     * @param propertyName 属性名
     * @param propertyValue 属性值
     * @param exceptionNameOrIdKeys 例外网格的名称或id的一部分
     */
    public static setSceneMesh(target: Scene | TransformNode | Array<Scene | TransformNode>, propertyName: string, propertyValue: any, exceptionNameOrIdKeys?: string[]): void {
        if (Array.isArray(target)) {
            target.forEach(tar => {
                this.setSceneMesh(tar, propertyName, propertyValue, exceptionNameOrIdKeys);
            });
            return;
        }

        const ms = target instanceof Scene ? target.meshes : Helper.getChildMeshes(target, false);
        const regs: RegExp[] = [];
        if (exceptionNameOrIdKeys) {
            for (const k of exceptionNameOrIdKeys) {
                regs.push(new RegExp(k, 'i'));
            }
        }
        for (const m of ms) {
            let sus = false;
            for (const reg of regs) {
                if (reg.test(m.name) || reg.test(m.id)) {
                    sus = true;
                    break;
                }
            }
            if (sus) continue;
            (m as unknown as any)[propertyName] = propertyValue;
        }
    }

}

Helper.initialize();


export { Helper };
export type { IPointsOptions, ISequenceStep };